In some high-density traffic cities like Bogotá, slow attention to traffic incidents remains an issue, and a method to deal with it is yet to be implemented. Quick response is important for most drivers. Those involved in accidents would have higher chances of survival, having an incident wouldn't be as time-consuming, and even those never involved in an incident would see the benefit of faster-moving traffic.
Let us define what we mean by traffic incident or accident. Firstly, an accident implies the happening of a crash, which could be one of a car against an object, or between vehicles. On the other hand, incident is a more general term, it refers to any event that negatively affects traffic flow, so in those terms, an accident is a specific kind of incident.
In 2019, Bogotá had the alarming rate of a traffic accident every five to six minutes, and in 2018 there were a total of 50 800 traffic incidents, a number so excessively high, that if it were to be reduced, a great quantity of time would be saved. This of course puts interest on projects trying to fix this problem, which can take many different approaches and focal points. Among these are some that recollect data for further study, others that aim to implement early incident detection and those that try to reduce the time spent on clearing these incidents by optimally assigning the resources necessary, which is what we worked on for this project.
We aim to make an algorithm that can provide ways to minimize the total time spent trying to clear incidents, by being able to optimally allocate resources. In this example we'll assign transit police officers to traffic incidents, but the same algorithm, coupled with the right datasets, could be used in a plethora of other ways, such as assigning tow-trucks to stranded vehicles, ambulances to life-threatening crashes, taxis to users requesting the service or, in an abstract sense, any pairing of sources-destinations with a cost per source-destination pairing.
The first step to this optimization is used in case there's a shortage in police officers. In this case, the best thing we can do is to select which incidents should be taken care of, such that the highest amount of individuals are accounted for considering the available officers. Of course, these would need to be tagged with a level of priority for this to be possible. Here, the priority is set by Bogota's mobility secretariat. It primarily takes into account the threat to the involved's life or health, and secondarily, how much traffic the incident is causing.
Once we know which incidents will be taken care of, we'll need to know which officer will be assigned to each selected incident. These pairings are made taking into account the total time each configuration would take to clear the incidents.
In this example the algorithm will be run at every hour, although this could be left to the user's choice and needs.
The model for the solution to our problem is divided into 2 parts, the first part is responsible for maximizing the total weights of the severity of the incidents that will be addressed with the response resources currently available, and the second part is responsible for minimizing the total travel time of the response of all units.
$\textbf{Phase 1:}$ The objective function in this phase is:
$(1a)$ $$\max \sum_{j=1}^{N_i}a_{s(j)}z_j$$
$\textit{under the restrictions}$:
$(1b)$ $$\sum_{i=1}^{N_t} x_{ij}\geq b_{s(j)} z_j,\hspace{1cm} \forall j\in N_i$$
$(1c)$ $$\sum_{j=1}^{N_i} x_{ij}\leq 1,\hspace{1cm} \forall i\in RU $$
$(1d)$ $$x_{ij}=0 \text{ o } 1, \hspace{1cm} \forall i\in RU, \forall j\in N_i$$
$(1e)$ $$z_{j}=0 \text{ o } 1, \hspace{1cm}, \forall j\in N_i$$
$\textbf{Phase 2: }$ The objective function in this phase is:
$(2a)$ $$\min \sum_{i=1}^{N_t}\sum_{j=1}^{N_i} x_{ij}t_{ij}(t)$$
$\textit{under the restrictions}$:
$(2b)$ $$\sum_{i=1}^{N_t} x_{ij}\geq b_{s(j)} z_j,\hspace{1cm} \forall j\in N_i$$
$(2c)$ $$\sum_{j=1}^{N_i} x_{ij}\leq 1,\hspace{1cm} \forall i\in RU $$
$(2d)$ $$x_{ij}=0 \text{ o } 1, \hspace{1cm} \forall i\in RU, \forall j\in N_i$$
$\textit{Where}$:
$t_{ij}(t): $ shortest path travel time between points $i$, $j$ in time step $t$
$S(j): $ The function that returns the priority of a given incident $j$ (this is a number between 1 and 4, where 1 is the lowest priority)
$a_l: $ The weight of the severity of incidents with l-th priority
$b_l:$ The number of cranes needed to be dispatched to an incident with lth priority (2 needed for a priority of 4, 1 for a priority of 3, 1 for a priority of 2, and 0 for a priority of 1)
$N_i:$ The number of incidents that are waiting for cranes
$N_t:$ The number of cranes in the entire system
$RU: $ response units
$\textit{Decision variables}$
$z_j=1$ Whether the j-th incident will be handled by certain cranes, otherwise 0
$x_{ij}=1$ If crane i is dispatched to incident j, otherwise 0
In phase 1 of the model, the objective function (1a) is to maximize the total weights of the severity of the incidents to be treated without exceeding the limitation of the number of response units available. Constraint (1b) ensures that a sufficient quantity is sent cranes to each incident site. Restriction (1c) indicates that a crane can only handle a single incident in a Given moment. Constraints (1d) and (1e) represent integer constraints, Equation (2a) is in charge of minimizing the estimated travel type of units required to move from their current locations to the assigned incident locations.
This problem belongs to integer linear programming (since the objective function is linear, both in phase 1 and in phase 2 it is a dot product between 2 vectors, the constraints are linear and the choice variables are integers), since Basically, this problem is linear and has a solution through algorithms that involve convexity, but due to the integer restrictions, it turns out that this problem will not always have a solution. In fact, it is an NP-complete problem.
import numpy as np
import cvxpy as cp
import pandas as pd
import plotly.graph_objects as go
Load incidents dataset:
incidentsdf = pd.read_csv('data/incidents_may.csv',
usecols=[
"id", "priority", "implicated", "incident_time",
"latitude", "longitude"
])
# drop duplicates
incidentsdf = incidentsdf.drop_duplicates(subset="id", keep="first")
# cast incident_time to datetime
incidentsdf["incident_time"] = pd.to_datetime(incidentsdf["incident_time"])
# fill nan to 0 and cast implicated to int
incidentsdf["implicated"] = incidentsdf["implicated"].fillna(0)
incidentsdf["implicated"] = incidentsdf["implicated"].astype(int)
# calculate intervals and create column
new = incidentsdf.groupby(pd.Grouper(
key="incident_time", freq='1H')).apply(lambda x: x['incident_time'])
incidentsdf["interval"] = new.index.get_level_values(0)
# multiindex by interval, then id
incidentsdf = incidentsdf.set_index(["interval", "id"])
# print head
incidentsdf.head()
| priority | latitude | longitude | implicated | incident_time | ||
|---|---|---|---|---|---|---|
| interval | id | |||||
| 2022-04-30 23:00:00 | 57518 | 2 | 4.705279 | -74.127686 | 2 | 2022-04-30 23:59:00 |
| 57519 | 1 | 4.600571 | -74.143379 | 3 | 2022-04-30 23:54:00 | |
| 2022-05-01 00:00:00 | 57520 | 1 | 4.647777 | -74.137123 | 1 | 2022-05-01 00:08:00 |
| 57521 | 1 | 4.720405 | -74.075302 | 2 | 2022-05-01 00:10:00 | |
| 57522 | 1 | 4.614258 | -74.167252 | 2 | 2022-05-01 00:11:00 |
# number of incindents per interval
incidentsdf.groupby(["interval"]).size().sort_values(ascending=False)
interval
2022-05-20 18:00:00 50
2022-05-19 18:00:00 48
2022-05-24 17:00:00 48
2022-05-23 10:00:00 46
2022-05-20 20:00:00 44
..
2022-05-10 02:00:00 1
2022-05-23 02:00:00 1
2022-05-12 01:00:00 1
2022-05-11 04:00:00 1
2022-05-06 00:00:00 1
Length: 592, dtype: int64
Load agents dataset:
agentsdf = pd.read_csv(
'data/agents_may.csv',
usecols=["dev_id", "time_stamp", "latitude", "longitude"])
# cast time_stamp to datetime
agentsdf["time_stamp"] = pd.to_datetime(agentsdf["time_stamp"])
# calculate intervals and create column
new = agentsdf.groupby(pd.Grouper(key="time_stamp",
freq='1H')).apply(lambda x: x['time_stamp'])
agentsdf["interval"] = new.index.get_level_values(0)
# multiindex by interval, then id
agentsdf = agentsdf.set_index([
"interval", "dev_id"
]).reset_index().drop_duplicates(subset=["interval", "dev_id"],
keep="last").set_index(["interval", "dev_id"])
# print head
agentsdf.head()
| time_stamp | latitude | longitude | ||
|---|---|---|---|---|
| interval | dev_id | |||
| 2022-05-01 | 868033050102534 | 2022-05-01 00:01:42 | 4.650030 | -74.147322 |
| 868033050090085 | 2022-05-01 00:01:47 | 4.581688 | -74.128348 | |
| 868033050103508 | 2022-05-01 00:01:52 | 4.551098 | -74.147635 | |
| 868033050101650 | 2022-05-01 00:01:55 | 4.619840 | -74.159668 | |
| 868033050102989 | 2022-05-01 00:01:56 | 4.651116 | -74.136658 |
fixed_day = "2022-05-15"
incidentsdf_subset = incidentsdf[incidentsdf["incident_time"].dt.to_period("d")
== fixed_day]
agentsdf_subset = agentsdf[agentsdf["time_stamp"].dt.to_period("d") ==
fixed_day]
# format incident locations according to the routing API
incidents_locations = incidentsdf_subset[["longitude", "latitude"]].apply(
lambda x: ','.join(x.values.astype(str)), axis=1)
incidents_locations
interval id
2022-05-15 00:00:00 63576 -74.12568664550781,4.711182117462158
63577 -74.12039947509766,4.668450355529785
63578 -74.187255859375,4.615567684173584
63579 -74.08935546875,4.571099281311035
63580 -74.1615982055664,4.615560531616211
...
2022-05-15 22:00:00 63904 -74.10940551757812,4.679540634155273
2022-05-15 23:00:00 63905 -74.18651580810547,4.569133758544922
63906 -74.13054656982422,4.6656575202941895
63907 -74.0887451171875,4.725343227386475
63908 -74.13822174072266,4.557262420654297
Length: 333, dtype: object
# format agent locations according to the routing API
agents_locations = agentsdf_subset[["longitude", "latitude"]].apply(
lambda x: ','.join(x.values.astype(str)), axis=1)
agents_locations
interval dev_id
2022-05-15 00:00:00 868033050139668 -74.0573678,4.7182193
868033050103862 -74.0768264,4.6618975
868033050100603 -74.1171796,4.7138703
868033050138876 -74.1032533,4.7273083
868033050103508 -74.0851146,4.5977981
...
2022-05-15 23:00:00 868033050101908 -74.1627083,4.6149867
868033050102203 -74.2210367,4.7095467
868033050089939 -74.1495259,4.6356699
868033050099839 -74.1403013,4.6968925
868033050102005 -74.1546772,4.6312973
Length: 1211, dtype: object
Let's pick a fixed day for our example:
# select a subset of one day to display
fixed_day = "2022-05-15"
incidentsdf_subset = incidentsdf_subset.reset_index().copy()
agentsdf_subset = agentsdf_subset.reset_index().copy()
# add hour colums for slider purposes
incidentsdf_subset["hour"] = incidentsdf_subset[
"incident_time"].dt.hour.astype(str)
agentsdf_subset["hour"] = agentsdf_subset["time_stamp"].dt.hour.astype(str)
We will now plot the agents and incidents per hour in the day we fixed previously
hour = 0
agentsdf_subset_hour = agentsdf_subset[agentsdf_subset["hour"] == hour]
incidentsdf_subset_hour = incidentsdf_subset[incidentsdf_subset["hour"] ==
hour]
# hour range
hours = [str(i) for i in range(24)]
map_frames = []
# frames
for hour in hours:
agentsdf_subset_hour = agentsdf_subset[agentsdf_subset["hour"] == hour]
incidentsdf_subset_hour = incidentsdf_subset[incidentsdf_subset["hour"] ==
hour]
map_frames.append(
go.Frame(data=[
go.Scattermapbox(lat=agentsdf_subset_hour["latitude"],
lon=agentsdf_subset_hour["longitude"],
mode='markers',
marker=go.scattermapbox.Marker(
size=8, color='rgb(255, 0, 0)', opacity=0.7),
name="Agents"),
go.Scattermapbox(lat=incidentsdf_subset_hour["latitude"],
lon=incidentsdf_subset_hour["longitude"],
mode='markers',
marker=go.scattermapbox.Marker(
size=8, color='rgb(0, 0, 255)', opacity=0.7),
name="Incidents")
],
name=hour))
# hour_steps
hour_steps = []
for hour in hours:
slider_step = {
"args": [[hour], {
"frame": {
"duration": 300,
"redraw": True
},
"mode": "immediate",
"transition": {
"duration": 300
}
}],
"label":
hour,
"method":
"animate"
}
hour_steps.append(slider_step)
# hour slider
hour_slider = {
"active": 0,
"yanchor": "top",
"xanchor": "left",
"currentvalue": {
"font": {
"size": 20
},
"prefix": "Hour: ",
"visible": True,
"xanchor": "right"
},
"transition": {
"duration": 300,
"easing": "cubic-in-out"
},
"pad": {
"b": 10,
"t": 10
},
"len": 0.9,
"x": 0.1,
"y": 0,
"steps": hour_steps
}
fig = go.Figure(data=[
go.Scattermapbox(lat=agentsdf_subset_hour["latitude"],
lon=agentsdf_subset_hour["longitude"],
mode='markers',
marker=go.scattermapbox.Marker(size=8,
color='rgb(255, 0, 0)',
opacity=0.7),
name="Agents"),
go.Scattermapbox(lat=incidentsdf_subset_hour["latitude"],
lon=incidentsdf_subset_hour["longitude"],
mode='markers',
marker=go.scattermapbox.Marker(size=8,
color='rgb(0, 0, 255)',
opacity=0.7),
name="Incidents")
],
layout=go.Layout(
title=f"Incident and Agents in the {fixed_day}",
updatemenus=[{
"type":
"buttons",
"buttons": [{
"args": [
None, {
"frame": {
"duration": 500,
"redraw": True
},
"fromcurrent": True,
"transition": {
"duration": 300,
"easing": "quadratic-in-out"
}
}
],
"label":
"Play",
"method":
"animate"
}, {
"args": [[None], {
"frame": {
"duration": 0,
"redraw": True
},
"mode": "immediate",
"transition": {
"duration": 0
}
}],
"label":
"Pause",
"method":
"animate"
}],
"direction":
"left",
"pad": {
"r": 10,
"t": 87
},
"showactive":
False,
"type":
"buttons",
"x":
0.1,
"xanchor":
"right",
"y":
0,
"yanchor":
"top"
}],
autosize=True,
hovermode='closest',
showlegend=True,
mapbox=dict(bearing=0,
center=dict(lat=4.624335, lon=-74.063644),
pitch=0,
zoom=11,
style='open-street-map'),
height=800,
sliders=[hour_slider]),
frames=map_frames)
fig.show(renderer="notebook")
Fix a one hour to run the priorization algorithm.
fixed_hour = "15:00:00"
incidents_locations_hour = incidents_locations.xs(f"{fixed_day} {fixed_hour}")
agents_locations_hour = agents_locations.xs(f"{fixed_day} {fixed_hour}")
agents_locations_hour
dev_id
868033050089541 -74.1818917,4.6255967
868033050100017 -74.0998077,4.7375905
868033050140419 -74.1352384,4.8772434
868033050089442 -74.1696859,4.6203844
868033050099144 -74.1503911,4.5738665
...
868033050101692 -74.1043883,4.501005
868033050104324 -74.10813,4.6146817
868033050103854 -74.0955183,4.6194683
868033050102013 -74.1165764,4.6190523
868033050099128 -74.1049649,4.5037502
Length: 76, dtype: object
In order to get the travel times from each agent to each incident we used the OSRM API and the following code:
agents_coordinates = ";".join(agents_locations_hour)
incidents_coordinates = ";".join(incidents_locations_hour)
coordinates = agents_coordinates + ";" + incidents_coordinates
agents_count = agents_locations_hour.shape[0]
sources = ";".join(str(i) for i in range(agents_count))
incidents_count = incidents_locations_hour.shape[0]
destinations = ";".join(
str(i) for i in range(agents_count, agents_count + incidents_count)
)
# Ojo con correr esto sin estar corriendo el servicio.
durations = json.loads(
requests.get(
f"http://127.0.0.1:5000/table/v1/driving/{coordinates}?sources={sources}&destinations={destinations}"
).text
)["durations"]
For this example though, we have alredy consulted the travel time and are now saved in the durations.csv file.
Now let's run the model:
t = pd.read_csv("data/durations.csv") # duration matrixj
i = t.shape[0] # number of agents
j = t.shape[1] # number of incidents
b = np.array([np.random.randint(1, 4) for i in range(j)])
x = cp.Variable((i, j), boolean=True) # agents-incidents matrix
z = cp.Variable(j, boolean=True) # incidents coverage vector
objective = cp.Maximize(b @ z)
constraints = [
cp.sum(x, axis=0) >= cp.multiply(b, z), # required agents per incidents
cp.sum(x, axis=1) <= 1 # one agent covers at max. one incident
]
problem = cp.Problem(objective, constraints)
problem.solve(verbose=0)
31.0
print("Agents :\n", x.value, end="\n\n")
print("Incidents:\n", z.value)
Agents : [[0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 1. 0. ... 0. 0. 0.] ... [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 0. 0.] [0. 0. 0. ... 0. 1. 0.]] Incidents: [1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
objective = cp.Minimize(cp.sum(cp.sum(cp.multiply(x, t), axis=0)))
constraints = [
cp.sum(x, axis=0) >= cp.multiply(b,
z.value), # required agents per incidents
cp.sum(x, axis=1) <= 1 # one agent covers at max. one incident
]
problem = cp.Problem(objective, constraints)
problem.solve()
9862.9
x = pd.DataFrame(x.value)
agents_selected = x.sum(axis=1) > 0
x = x[agents_selected].astype(int)
The matrix below represents the response strategy suggested by the algorithm. In here each row represent an agent and each column represents an incident, therefore a 1 on the ith row and jth column indicated that the agent i will responde to the incident j.
x
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 |
| 4 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 8 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 10 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 12 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 15 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 20 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
| 21 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
| 25 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 27 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 28 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 29 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 30 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
| 33 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 34 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 37 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 38 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| 42 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 43 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 47 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 49 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 52 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 55 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 57 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 59 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 63 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 68 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 70 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 |
| 74 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Dispatch pairings:
response_dispatch = np.array(x[x == 1].stack().index)
response_dispatch
array([(1, 0), (3, 15), (4, 3), (5, 16), (8, 11), (10, 17), (12, 6),
(15, 4), (20, 19), (21, 13), (25, 5), (27, 4), (28, 12), (29, 12),
(30, 18), (33, 16), (34, 1), (37, 8), (38, 17), (42, 7), (43, 14),
(47, 0), (49, 1), (52, 2), (55, 14), (57, 5), (59, 9), (63, 4),
(68, 12), (70, 16), (74, 10)], dtype=object)
agentsdf_subset_hour = agentsdf_subset.set_index(
["interval", "dev_id"]).xs(f"{fixed_day} {fixed_hour}")
incidentsdf_subset_hour = incidentsdf_subset.set_index(
["interval", "id"]).xs(f"{fixed_day} {fixed_hour}")
lines = []
for agent_idx, incident_idx in response_dispatch:
agent_coords = agentsdf_subset_hour.iloc[agent_idx, [1, 2]]
incident_coords = incidentsdf_subset_hour.iloc[incident_idx, [1, 2]]
coords = [[x, y] for x, y in zip(agent_coords, incident_coords)]
line = go.Scattermapbox(lat=coords[0], lon=coords[1], mode="lines")
lines.append(line)
fig = go.Figure(
data=[
go.Scattermapbox(lat=agentsdf_subset_hour["latitude"],
lon=agentsdf_subset_hour["longitude"],
mode='markers',
marker=go.scattermapbox.Marker(
size=8, color='rgb(255, 0, 0)', opacity=0.7),
name="Agents"),
go.Scattermapbox(lat=incidentsdf_subset_hour["latitude"],
lon=incidentsdf_subset_hour["longitude"],
mode='markers',
marker=go.scattermapbox.Marker(
size=8, color='rgb(0, 0, 255)', opacity=0.7),
name="Incidents",
text=b)
] + lines,
layout=go.Layout(
title=f"Incident and Agents in the {fixed_day}",
autosize=True,
hovermode='closest',
showlegend=True,
mapbox=dict(bearing=0,
center=dict(lat=4.624335, lon=-74.063644),
pitch=0,
zoom=11,
style='open-street-map'),
height=800,
),
)
fig.show(renderer="notebook")
As it was shown, it is possible to reduce the total time spent on traffic-jams caused by incidents, by making the right allocations. Each of the two parts the proyect showed are essential for a way to completlely optimize the time spent taking care of incidents, we hope this can be continued to work on and developed further on, as there is still room for add-ons that may help bring down the wasted time. \ The parts it was divided in also portrayed how optimization problems often need to be split into different problems or stages. An idea of what could be developed later on is a way to minimize the quantity of active traffic police officers at a certain time, or maximize available officers in a certain area where incidents are more common.\ This of course also applies to any of the other similar applications this process could have, some of which were mentioned previously. We hope we can make real use of this algorithm, as we know Bogotá's mobility secretariat doesn't have an optimization method yet for this specific kind of ressource allocation.